package org.nacos.provider.controller;

import java.util.List;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.nacos.provider.converter.BaseConverter;
import org.nacos.provider.dataobject.ProductDO;
import org.nacos.provider.entity.PageBaseDTO;
import org.nacos.provider.entity.ProductDTO4;
import org.nacos.provider.service.ProductService;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class ProductController {

	/**
	 * nacos 客户端部署 (http://localhost:8488/nacos/)
	 * 部署模式: 
	 *   单机模式 - 用于测试和单机试用
	 *   集群模式 - 用于生产环境，确保高可用
	 *   多集群模式 - 用于多数据中心场景
	 * 配置文件: nacos/conf/application.properties
	 *   默认端口 
	 *     server.port=8848
	 *   添加 MySQL 数据源信息 (mysql8.0之后的版本需要加 mysql 时区的设置 &serverTimezone=UTC)
	 *     spring.datasource.platform=mysql
	 *     db.num=1
	 *     db.url.0=jdbc:mysql://localhost:3306/test_nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
	 *     db.user=root
	 *     db.password=root
	 * 执行 Mysql 数据库脚本: /nacos/conf/nacos-mysql.sql
	 * 启动/停止(windows 需要改为单节点模式)
	 * 注意: Nacos 默认不支持 https，如果有需要，则需要在 Nginx 端配置，把 443 端口映射到 Nacos 的 8848 端口，然后在 Nginx 上管理证书
	 */

	/**
	 * Nacos (Naming and Configuration Service 命名和配置服务)
	 * 概念：
	 * 服务 (Service): 服务是指一个或一组软件功能（例如特定信息的检索或一组操作的执行），其目的是不同的客户端可以为不同的目的重用（例如通过跨进程的网络调用）
	 * 服务注册中心 (Service Registry): 服务注册中心，它是服务，其实例及元数据的数据库。服务实例在启动时注册到服务注册表，并在关闭时注销。服务和路由器的客户端查询服务注册表以查找服务的可用实例。服务注册中心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求 
	 * 服务元数据 (Service Metadata): 服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据
	 * 服务提供方 (Service Provider): 是指提供可复用和可调用服务的应用方
	 * 服务消费方 (Service Consumer): 是指会发起对某个服务调用的应用方
	 * 配置 (Configuration): 在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理，以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物（如 WAR，JAR 包等）更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中，由系统管理员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一
	 * 配置管理 (Configuration Management): 在数据中心中，系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理
	 * 名字服务 (Naming Service): 提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务，例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景
	 * 配置服务 (Configuration Service): 在服务或者应用运行过程中，提供动态配置或者元数据以及配置管理的服务提供者
	 * 
	 * 原理：
	 * 数据模型: 一般推荐使用由运行环境作为命名空间、应用名作为分组和服务功能作为服务名的组合来确保该服务的唯一性
	 *  - Namespace: 命名空间，默认是空串，公共命名空间是 public。用于进行租户粒度的配置隔离，不同的命名空间下，可以存在相同的 Group 或 Data ID 的配置
	 *  - Group: 分组，分组默认是 DEFAULT_GROUP
	 *  - Service/ DataId: 服务名/ 数据 ID
	 * 
	 * 通信机制:
	 *  Nacos 1.X 使用的是 HTTP 1.1 短链接来模拟长链接，每 30s 发一个心跳跟 Server 端，对比客户端配置 MD5 值是否跟 Server 保持一致，如果一致就继续保持住链接，否则就把不一致的配置返回，然后客户端再获取最新配置值
	 *  Nacos 2.x 升级为长连接模式，如果配置变更则建立长链接，服务端再向客户端推送变更配置列表，然后客户端再主动拉取配置更新，此种方式大幅提升了通信效率
	 *  Nacos 2.0 版本相比 1.X 新增了gRPC 的通信方式，因此需要增加 2 个端口，是在配置的主端口（server.port）基础上，进行一定偏移量自动生成
	 *  - 端口 9848，主端口偏移 1000，客户端 gRPC 请求服务端端口，用于客户端向服务端发起连接和请求
	 *  - 端口 9849，主端口偏移 1001，服务端 gRPC 请求服务端端口，用于服务间同步等
	 * 
	 * 一致性协议: Nacos 组件内部需要用到数据存储，单机环境下使用简单的内嵌关系型数据库即可搞定，但是在集群模式下，就需要考虑各个节点之间的数据一致性以及数据同步性问题，而要解决这个问题，就必须引入一致性协议来保证各个节点之间的数据的一致性
	 *  从服务注册来看: 
	 *   - 如果是非持久化服务，即需要客户端主动发送心跳续约的场景，这种情况，如果是数据丢失，是可以通过客户端心跳补偿的，所以强一致性共识算法在这里不合适了，否则如果没有过半节点存活就会导致整个集群罢工，所以这是选择了最终一致性算法（Distro）
	 *   - 如果是持久化服务，即通过 Nacos Server 创建的服务，这时则需要 Nacos 来确保所有节点的数据一致性，所以这里最终一致算法就不合适了，必须选择强一致性共识算法
	 *  从服务配置来看: 因为配置数据都是在 Nacos 服务端进行创建和管理的，必须在所有节点完成同步的情况下才认为是保存成功了，所以，必须保证集群中大部分的节点是强一致的，所以，配置管理使用的是强一致性算法（Raft）
	 * 
	 * 负载均衡: 提供了基于健康检查、权重、以及基于第三方 CMDB 的标签负载均衡器。当服务部署在多个地域时，使用标签负载均衡器可以实现同标签优先访问的流量调度策略，即可以用来实现服务的就近访问，标签负载均衡器可以支持其他更多的场景
	 *   目的: 防止流量异常压垮服务提供者的服务，所以服务提供者需要能够完全掌控服务的流量调配，并可以动态调整。
	 * 
	 * 实例分类: 
	 *   临时实例: 临时存储于注册中心，临时实例自动发送心跳上报健康状态，发送心跳的周期默认是 5 秒，Nacos 服务端在 15 秒没收到心跳后将实例设置为不健康，在 30 秒没收到心跳时将这个临时实例摘除。
	 *           对于临时实例，如果健康检查失败，则直接从注册中心直接删除。所以，临时实例就比较适合用来应对流量突增的场景，服务实例需要动态弹性扩容，当突增流量过后，把扩容新增的服务实例停掉，就能可以从注册中心快速移除了。
	 *   持久实例: 永久存储于注册中心，Nacos 服务端使用反向探测的方式进行健康检查，除非调用接口主动注销，否则服务端无法摘除不健康实例。
	 *           对于持久化实例，如果健康检查失败，会被标记成不健康状态，但是不会主动删除，但是可以方便运维人员看到实例的实时健康状态，比如可以设置警告、扩容等一些列提前预防措施。
	 * 
	 * 健康检查: 
	 *   检查机制: Nacos 既支持客户端的主动心跳上报健康检查，也支持服务端的探测健康检查，具体得根据不同的实例类型，见上面的实例分类说明，同一个服务可以切换不同的健康检查模式。
	 *            Nacos 健康检查以指定方式检查服务下注册的实例的健康度，从而确认该实例是否能提供服务。根据检查结果，实例会被判断为健康或不健康，在对服务发起解析请求时，不健康的实例不会返回给客户端。
	 *   健康阈值: Nacos 为了防止因过多实例不健康导致流量全部流向健康实例，继而造成流量压力把健康实例压垮并形成雪崩效应，应将健康保护阈值定义为一个 0 到 1 之间的浮点数。
	 *            当域名健康实例数占总服务实例数的比例小于该值时，无论实例是否健康，都会将这个实例返回给客户端，这样做虽然损失了一部分流量，但是保证了集群中剩余健康实例能正常工作。
	 * 
	 * 鉴权: 即验证谁是否能够对某个东西进行某种操作
	 *   Nacos 2.1.0+ 版本开始，支持通过 SPI 的方式注入鉴权相关插件，并在 application.properties 配置文件中选择某一种插件实现作为实际鉴权服务
	 *   Nacos 在设计鉴权插件时，将鉴权信息主要抽象为身份信息，资源和操作类型 3 类主要概念。比如，服务端启用鉴权之后，需要用户名和密码登录之后才能正常使用 Nacos
	 *   Nacos 是一个基于 Spring Boot 开发的项目，内部使用了 Spring Security 作为权限认证框架
	 * 
	 * 性能测试: (官方参考数据)
	 *   服务发现（Nacos 2.0）: 
	 *    - 服务及实例容量达到百万级，集群运行持续稳定
	 *    - 注册/注销实例 TPS 达到 26000 以上，总体较 Nacos 1.X 提升至少 2 倍
	 *    - 查询实例 TPS 能够达到 30000 以上，总体较Nacos 1.X 提升 3 倍左右
	 *   配置管理（Nacos 2.0）:
	 *   Nacos 2.0 读 QPS 14000 QPS，对比Nacos 1.X QPS 8000 提升 75%
	 *   Nacos 2.0 写 TPS 2500 TPS，对比 Nacos 1.X 无明显提升
	 *   Nacos 2.0 支撑长链接 40000 以上，对比 Nacos 1.X 提升 7 倍以上
	 *   Nacos 2.0 变更推送1500/s, 对比 Nacos 1.X 无明显提升
	 */
	
	/**
	 * 服务注册发现机制: 
	 *   提供者服务向注册中心注册，如果是临时实例，实例会定时（默认：5 秒）向注册中心发送心跳以维护活动，如果是持久实例，注册中心会通过 API 主动检测提供者健康状态
	 *   消费者服务向注册中心订阅并获取提供者服务地址列表，并每隔一段时间（默认：10 秒）就重新拉取一次服务实例列表并缓存到本地，如果提供者服务状态有变更，注册中心也会推送通知给消费者
	 *   消费者根据负载均衡算法选择一个实例进行调用
	 * 服务/实例信息: 
	 *   服务包括以下几个基本信息: 
	 *    - 命名空间（Namespace）
	 *    - 分组（Group）
	 *    - 服务名（Name）
	 *   服务元数据进一步定义了 Nacos 中服务的细节属性和描述信息，主要包含以下信息:
	 *    - 健康保护阈值（ProtectThreshold）: 为防止因过多实例故障，导致过多流量流入剩余实例设置的阈值。
	 *    - 实例选择器: 用于在获取服务下的实例列表时，过滤和筛选实例。
	 *    - 拓展数据（extendData）: 用户在注册实例时自定义扩展的元数据内容。
	 *   服务实例是提供服务的具体节点，主要存储该实例的 一些网络相关的基础信息: 
	 *    - 网络 IP 地址: 实例 IP 地址，Nacos 2.x 可以为域名。
	 *    - 网络端口: 该实例的端口信息。
	 *    - 健康状态（Healthy）: 实例健康状态。
	 *    - 集群（Cluster）: 实例归属的逻辑集群标识。
	 *    - 拓展数据（extendData）: 用户自定义扩展的元数据内容。
	 *  服务实例元数据主要用于实例运维相关的数据信息，主要包含以下信息: 
	 *    - 权重（Weight）: 权重为浮点数，范围为 0-10000，权重越大，实例分配的流量越大。
	 *    - 上线状态（Enabled）: 标记该实例是否接受流量，可快速将某个实例从服务中移除。
	 *    - 拓展数据（extendData）: 在不变动实例本身的情况下，可快速地修改和新增实例的扩展数据。
	 */
	
	@Resource
	ProductService productService;
	
	@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
	public String echo(@PathVariable String string) {
		return "Hello Nacos Discovery " + string;
	}
	
	@RequestMapping(value = "/product/{id}", method = RequestMethod.GET)
	public Map<String, Object> queryProductInfo(@PathVariable("id") Integer productId) {
		return productService.queryProductInfo(productId);
	}

	@RequestMapping(value = "/queryProduct", method = RequestMethod.GET)
	public List<ProductDO> queryProduct(@RequestParam("id") Integer productId) {
		return productService.queryProduct(productId);
	}

	@RequestMapping(value = "/queryProductPage", method = RequestMethod.GET)
	public PageBaseDTO<ProductDO> queryProductPage(Integer pageNo, Integer pageSize) {
		return productService.queryProductPage(pageNo, pageSize);
	}
	
	@RequestMapping(value = "/product/convert", method = RequestMethod.GET)
	public String productConvert() {
		return productService.productConvert();
	}

	/**
	 * validation 校验方式
	 * 1. 使用注解 @Validated，抛异常，可指定分组(value={ProductDTO4.xxx.class})
	 * 2. @Validated 配合 BindingResult，自定义处理方式
	 * 3. 使用 Validator 代码内手动验证, 可用参数指定分组
	 * 4. 可自定义注解校验
	 */

	@RequestMapping(value = "/product/valid")
	public String productValid(@Validated(value={ProductDTO4.Add.class}) ProductDTO4 productDTO4, BindingResult bindingResult) {
		log.info(productDTO4.toString());
		// 获取校验错误的提示信息，如果有错误提示信息
		if (bindingResult.hasErrors()) {
			// bindingResult.getFieldErrors() 字段的错误
			for (FieldError fieldError : bindingResult.getFieldErrors()) {
				// fieldError.getField() 绑定失败的字段名；fieldError.getDefaultMessage() 默认的错误提示信息
				log.info("注解校验 " + fieldError.getField() + " : " + fieldError.getDefaultMessage());
			}
			// bindingResult.getAllErrors() 所有错误
			for (ObjectError objectError : bindingResult.getAllErrors()) {
				log.info("注解校验 " + objectError.getDefaultMessage());
			}
		}
		return productService.productValid(productDTO4);
	}

	@Resource
	List<BaseConverter> converterList;

	@Resource
	Map<String, BaseConverter> converterMap;

	@RequestMapping(value = "/product/auto")
	public String productValid() {
		for (BaseConverter converter : converterList) {
			converter.print("list:");
		}
		for (Map.Entry<String, BaseConverter> entry : converterMap.entrySet()) {
			log.info(entry.getKey());
			entry.getValue().print("map:");
		}
		return "success";
	}

}
